﻿<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>GoJS Shapes -- Northwoods Software</title>
  <!-- Copyright 1998-2020 by Northwoods Software Corporation. -->
  <script src="../release/go.js"></script>
  <script src="../extensions/Figures.js"></script>
  <script src="goIntro.js"></script>
</head>
<body onload="goIntro()">
<div id="container" class="container-fluid">
<div id="content">

<h1>Shapes</h1>
<p>
Use the <a>Shape</a> class to paint a geometrical figure.
You can control what kind of shape is drawn and how it is stroked and filled.
</p>
<p>
Shapes, like <a>TextBlock</a>s and <a>Picture</a>s, are "atomic" objects -- they cannot contain any other objects.
So a Shape will never draw some text or an image.
</p>
<p>
In these simplistic demonstrations, the code programmatically creates a Part and adds it to the Diagram.
Once you learn about models and data binding you will generally not create parts (nodes or links) programmatically.
</p>

<h2 id="Figures">Figures</h2>
<p>
You can set the <a>Shape.figure</a> property to commonly named kinds of shapes.
When using <a>GraphObject,make</a>, you can pass the figure name as a string argument.
You may also need to set the <a>GraphObject.desiredSize</a> or <a>GraphObject.width</a> and <a>GraphObject.height</a> properties,
although it is also common to have the size determined by the Panel that the shape is in.
</p>
<p>
Here are several of the most often used Shape figures:
</p>
<pre class="lang-js" id="figureShapes">
  diagram.add(
    $(go.Part, "Horizontal",
      $(go.Shape, "Rectangle",        { width: 40, height: 60, margin: 4, fill: null }),
      $(go.Shape, "RoundedRectangle", { width: 40, height: 60, margin: 4, fill: null }),
      $(go.Shape, "Ellipse",          { width: 40, height: 60, margin: 4, fill: null }),
      $(go.Shape, "Diamond",          { width: 40, height: 60, margin: 4, fill: null }),
      $(go.Shape, "TriangleRight",    { width: 40, height: 60, margin: 4, fill: null }),
      $(go.Shape, "TriangleDown",     { width: 40, height: 60, margin: 4, fill: null }),
      $(go.Shape, "TriangleLeft",     { width: 40, height: 60, margin: 4, fill: null }),
      $(go.Shape, "TriangleUp",       { width: 40, height: 60, margin: 4, fill: null }),
      $(go.Shape, "MinusLine",        { width: 40, height: 60, margin: 4, fill: null }),
      $(go.Shape, "PlusLine",         { width: 40, height: 60, margin: 4, fill: null }),
      $(go.Shape, "XLine",            { width: 40, height: 60, margin: 4, fill: null })
    ));
</pre>
<script>goCode("figureShapes", 600, 100)</script>
<p>
You can see all of the named geometrical figures in the
<a href="../samples/shapes.html" target="samples">shapes</a> sample.
Some of the most commonly used figures are predefined in the <b>GoJS</b> library.
But most figures are defined in the <a href="../extensions/Figures.js" target="_blank">Figures.js</a> file
in the extensions directory.
</p>

<h2 id="FillAndStrokes">Fill and Strokes</h2>
<p>
The <a>Shape.stroke</a> property specifies the brush used to draw the shape's outline.
The <a>Shape.fill</a> property specifies the brush used to fill the shape's outline.
Additional "stroke..." properties also control how the shape's outline is drawn.
The most common such property is <a>Shape.strokeWidth</a>.
</p>
<pre class="lang-js" id="strokedShapes">
  diagram.add(
    $(go.Part, "Horizontal",
      $(go.Shape, { figure: "Club", width: 40, height: 40, margin: 4
                    }),  // default fill and stroke are "black"
      $(go.Shape, { figure: "Club", width: 40, height: 40, margin: 4,
                    fill: "green" }),
      $(go.Shape, { figure: "Club", width: 40, height: 40, margin: 4,
                    fill: "green", stroke: null }),
      $(go.Shape, { figure: "Club", width: 40, height: 40, margin: 4,
                    fill: null, stroke: "green" }),
      $(go.Shape, { figure: "Club", width: 40, height: 40, margin: 4,
                    fill: null, stroke: "green", strokeWidth: 3 }),
      $(go.Shape, { figure: "Club", width: 40, height: 40, margin: 4,
                    fill: null, stroke: "green", strokeWidth: 6 }),
      $(go.Shape, { figure: "Club", width: 40, height: 40, margin: 4,
                    fill: "green", background: "orange" })
    ));
</pre>
<script>goCode("strokedShapes", 600, 100)</script>

<p>
The <a>Shape.stroke</a> and <a>Shape.fill</a> properties take <a>Brush</a>es
but most often are given a CSS color string to denote solid color brushes.
These two properties default to a solid black brush.
However it is common to assign one of them to be either null or "transparent".
A null brush means that nothing is drawn for that stroke or fill.
A transparent brush produces the same appearance but different hit-testing behavior.
A shape with a null <a>Shape.fill</a> produces a "hollow" shape -- clicking inside
the shape will not "hit" that shape and thus not select the <a>Node</a> that that shape is in.
But a shape with a transparent fill produces a "filled" shape -- a mouse event inside the
shape will "hit" that shape.
</p>
<pre class="lang-js" class="lang-js" id="fill">
  diagram.div.style.background = "lightgray";
  diagram.add(
    $(go.Part, "Table",
      $(go.Shape, { row: 0, column: 0, figure: "Club", width: 60, height: 60, margin: 4,
                    fill: "green" }),
      $(go.TextBlock, "green", { row: 1, column: 0 }),
      $(go.Shape, { row: 0, column: 1, figure: "Club", width: 60, height: 60, margin: 4,
                    fill: "white" }),
      $(go.TextBlock, "white", { row: 1, column: 1 }),
      $(go.Shape, { row: 0, column: 2, figure: "Club", width: 60, height: 60, margin: 4,
                    fill: "transparent" }),
      $(go.TextBlock, "transparent", { row: 1, column: 2 }),
      $(go.Shape, { row: 0, column: 3, figure: "Club", width: 60, height: 60, margin: 4,
                    fill: null }),
      $(go.TextBlock, "null", { row: 1, column: 3 })
    ));
</pre>
<script>goCode("fill", 600, 100)</script>
<p>
Try clicking inside each of the shapes to see which ones will respond to the click and cause the whole panel to be selected.
Note that with the "transparent" fill you can see the diagram background, yet when you click in it you "hit" the Shape.
Only the last one, with a null fill, is truly "hollow".
Clicking in the last shape will only result in a click on the diagram background, unless you click on the stroke outline.
</p>

<h2 id="Geometry">Geometry</h2>
<p>
Every <a>Shape</a> gets its "shape" from the <a>Geometry</a> that it uses.
A Geometry is just a saved description of how to draw some lines given a set of points.
Setting <a>Shape.figure</a> uses a named predefined geometry that can be parameterized.
In general it is most efficient to give a Shape a Geometry rather than giving it a figure.
</p>
<p>
If you want something different from all of the predefined figures in <b>GoJS</b>,
you can construct your own Geometry and set <a>Shape.geometry</a>.
One way of building your own <a>Geometry</a> is by building <a>PathFigure</a>s
consisting of <a>PathSegment</a>s.
This is often necessary when building a geometry whose points are computed based on some data.
</p>
<p>
But an easier way to create constant geometries is by
calling <a>Geometry,parse</a> to read a string that has a geometry-defining path expression,
or to set <a>Shape.geometryString</a> to such a string.
These expressions have commands for moving an imaginary "pen".
The syntax for geometry paths is documented in the <a href="geometry.html">Geometry Path Strings</a> page.
</p>
<p>
This example creates a Geometry that looks like the letter "W"
and uses it in several Shape objects with different stroke characteristics.
Geometry objects may be shared by multiple Shapes.
Note that there may be no need to specify the <a>GraphObject.desiredSize</a> or <a>GraphObject.width</a> and <a>GraphObject.height</a>,
because the Geometry defines its own size.
If the size is set or if it is imposed by the containing Panel, the effective geometry is determined by the <a>Shape.geometryStretch</a> property.
Depending on the value of the geometryStretch property, this may result in extra empty space or the clipping of the shape.
</p>
<pre class="lang-js" id="geometries">
  var W_geometry = go.Geometry.parse("M 0,0 L 10,50 20,10 30,50 40,0", false);
  diagram.add(
    $(go.Part, "Horizontal",
      $(go.Shape, { geometry: W_geometry, strokeWidth: 2 }),
      $(go.Shape, { geometry: W_geometry, stroke: "blue", strokeWidth: 10,
                    strokeJoin: "miter", strokeCap: "butt" }),
      $(go.Shape, { geometry: W_geometry, stroke: "blue", strokeWidth: 10,
                    strokeJoin: "miter", strokeCap: "round" }),
      $(go.Shape, { geometry: W_geometry, stroke: "blue", strokeWidth: 10,
                    strokeJoin: "miter", strokeCap: "square" }),
      $(go.Shape, { geometry: W_geometry, stroke: "green", strokeWidth: 10,
                    strokeJoin: "bevel", strokeCap: "butt" }),
      $(go.Shape, { geometry: W_geometry, stroke: "green", strokeWidth: 10,
                    strokeJoin: "bevel", strokeCap: "round" }),
      $(go.Shape, { geometry: W_geometry, stroke: "green", strokeWidth: 10,
                    strokeJoin: "bevel", strokeCap: "square" }),
      $(go.Shape, { geometry: W_geometry, stroke: "red", strokeWidth: 10,
                    strokeJoin: "round", strokeCap: "butt" }),
      $(go.Shape, { geometry: W_geometry, stroke: "red", strokeWidth: 10,
                    strokeJoin: "round", strokeCap: "round" }),
      $(go.Shape, { geometry: W_geometry, stroke: "red", strokeWidth: 10,
                    strokeJoin: "round", strokeCap: "square" }),
      $(go.Shape, { geometry: W_geometry, stroke: "purple", strokeWidth: 2,
                    strokeDashArray: [4, 2] }),
      $(go.Shape, { geometry: W_geometry, stroke: "purple", strokeWidth: 2,
                    strokeDashArray: [6, 6, 2, 2] })
    ));
</pre>
<script>goCode("geometries", 600, 100)</script>

<h2 id="AngleAndScale">Angle and Scale</h2>
<p>
Besides setting the <a>GraphObject.desiredSize</a> or <a>GraphObject.width</a> and <a>GraphObject.height</a> to declare the size of a <a>Shape</a>,
you can also set other properties to affect the appearance.
For example, you can set the <a>GraphObject.angle</a> and <a>GraphObject.scale</a> properties.
</p>
<pre class="lang-js" id="transformedShapes">
  diagram.add(
    $(go.Part, "Table",
      $(go.Shape, { row: 0, column: 1,
                    figure: "Club", fill: "green", width: 40, height: 40,
                    }),  // default angle is zero; default scale is one
      $(go.Shape, { row: 0, column: 2,
                    figure: "Club", fill: "green", width: 40, height: 40,
                    angle: 30 }),
      $(go.Shape, { row: 0, column: 3,
                    figure: "Club", fill: "green", width: 40, height: 40,
                    scale: 1.5 }),
      $(go.Shape, { row: 0, column: 4,
                    figure: "Club", fill: "green", width: 40, height: 40,
                    angle: 30, scale: 1.5 })
    ));
</pre>
<script>goCode("transformedShapes", 600, 100)</script>

<p>
The <a>Shape.fill</a> and <a>GraphObject.background</a> brushes scale and rotate along with the shape.
The <a>GraphObject.areaBackground</a> is drawn in the containing panel's coordinates,
so it is not affected by the object's scale or angle.
</p>
<p>
The following two shapes each use three separate linear gradient brushes, one for each of the three properties.
Note the unrotated shape on the left.  Because its <a>GraphObject.background</a> brush is opaque,
you cannot see the <a>GraphObject.areaBackground</a> brush that fills the same area behind it.
</p>
<pre class="lang-js" id="backgrounds">
  var bluered = $(go.Brush, "Linear", { 0.0: "blue", 1.0: "red" });
  var yellowgreen = $(go.Brush, "Linear", { 0.0: "yellow", 1.0: "green" });
  var grays = $(go.Brush, "Linear", { 0.0: "black", 1.0: "lightgray" });

  diagram.add(
    $(go.Part, "Table",
      $(go.Shape, { row: 0, column: 0,
                    figure: "Club", width: 40, height: 40, angle: 0, scale: 1.5,
                    fill: bluered,
                    background: yellowgreen,
                    areaBackground: grays
                  }),
      $(go.Shape, { row: 0, column: 1, width: 10, fill: null, stroke: null }),
      $(go.Shape, { row: 0, column: 2,
                    figure: "Club", width: 40, height: 40, angle: 45, scale: 1.5,
                    fill: bluered,
                    background: yellowgreen,
                    areaBackground: grays
                  })
    ));
</pre>
<script>goCode("backgrounds", 600, 120)</script>

<h2 id="CustomFigures">Custom Figures</h2>
<p>
As shown above, one can easily create custom shapes just by setting <a>Shape.geometry</a> or <a>Shape.geometryString</a>.
This is particularly convenient when importing SVG.
However it is also possible to define additional named figures, which is convenient when you want to be able to easily
specify or change the geometry of an existing Shape by setting or data binding the <a>Shape.figure</a> property.
</p>
<p>
The static function <a>Shape,defineFigureGenerator</a> can be used to define new figure names.
The second argument is a function that is called with the <a>Shape</a> and the expected width and height
in order to generate and return a <a>Geometry</a>.
This permits parameterization of the geometry based on properties of the Shape and the expected size.
In particular, the <a>Shape.parameter1</a> and <a>Shape.parameter2</a> properties can be considered,
in addition to the width and height, while producing the Geometry.
To be valid, the generated Geometry bounds must be equal to or less than the supplied width and height.
</p>
<pre class="lang-js" id="defineFigure">
  go.Shape.defineFigureGenerator("RoundedTopRectangle", function(shape, w, h) {
    // this figure takes one parameter, the size of the corner
    var p1 = 5;  // default corner size
    if (shape !== null) {
      var param1 = shape.parameter1;
      if (!isNaN(param1) && param1 >= 0) p1 = param1;  // can't be negative or NaN
    }
    p1 = Math.min(p1, w / 2);
    p1 = Math.min(p1, h / 2);  // limit by whole height or by half height?
    var geo = new go.Geometry();
    // a single figure consisting of straight lines and quarter-circle arcs
    geo.add(new go.PathFigure(0, p1)
             .add(new go.PathSegment(go.PathSegment.Arc, 180, 90, p1, p1, p1, p1))
             .add(new go.PathSegment(go.PathSegment.Line, w - p1, 0))
             .add(new go.PathSegment(go.PathSegment.Arc, 270, 90, w - p1, p1, p1, p1))
             .add(new go.PathSegment(go.PathSegment.Line, w, h))
             .add(new go.PathSegment(go.PathSegment.Line, 0, h).close()));
    // don't intersect with two top corners when used in an "Auto" Panel
    geo.spot1 = new go.Spot(0, 0, 0.3 * p1, 0.3 * p1);
    geo.spot2 = new go.Spot(1, 1, -0.3 * p1, 0);
    return geo;
  });

  go.Shape.defineFigureGenerator("RoundedBottomRectangle", function(shape, w, h) {
    // this figure takes one parameter, the size of the corner
    var p1 = 5;  // default corner size
    if (shape !== null) {
      var param1 = shape.parameter1;
      if (!isNaN(param1) && param1 >= 0) p1 = param1;  // can't be negative or NaN
    }
    p1 = Math.min(p1, w / 2);
    p1 = Math.min(p1, h / 2);  // limit by whole height or by half height?
    var geo = new go.Geometry();
    // a single figure consisting of straight lines and quarter-circle arcs
    geo.add(new go.PathFigure(0, 0)
             .add(new go.PathSegment(go.PathSegment.Line, w, 0))
             .add(new go.PathSegment(go.PathSegment.Line, w, h - p1))
             .add(new go.PathSegment(go.PathSegment.Arc, 0, 90, w - p1, h - p1, p1, p1))
             .add(new go.PathSegment(go.PathSegment.Line, p1, h))
             .add(new go.PathSegment(go.PathSegment.Arc, 90, 90, p1, h - p1, p1, p1).close()));
    // don't intersect with two bottom corners when used in an "Auto" Panel
    geo.spot1 = new go.Spot(0, 0, 0.3 * p1, 0);
    geo.spot2 = new go.Spot(1, 1, -0.3 * p1, -0.3 * p1);
    return geo;
  });

  diagram.nodeTemplate =
      $(go.Part, "Spot",
        {
          selectionAdorned: false,  // don't show the standard selection handle
          resizable: true, resizeObjectName: "SHAPE",  // user can resize the Shape
          rotatable: true, rotateObjectName: "SHAPE",  // user can rotate the Shape
                                                       // without rotating the label
        },
        $(go.Shape,
          {
            name: "SHAPE",
            fill: $(go.Brush, "Linear", { 0.0: "white", 1.0: "gray" }),
            desiredSize: new go.Size(100, 50)
          },
          new go.Binding("figure", "fig"),
          new go.Binding("parameter1", "p1")),
        $(go.Panel, "Vertical",
          $(go.TextBlock,
            new go.Binding("text", "fig")),
          $(go.TextBlock, { stroke: "blue" },
            new go.Binding("text", "parameter1", function(p1) { return p1; }).ofObject("SHAPE"))
        )
      );

  diagram.model = new go.Model([
    { fig: "RoundedTopRectangle" },
    { fig: "RoundedTopRectangle", p1: 0 },
    { fig: "RoundedTopRectangle", p1: 3 },
    { fig: "RoundedTopRectangle", p1: 10 },
    { fig: "RoundedTopRectangle", p1: 50 },
    { fig: "RoundedTopRectangle", p1: 250 },
    { fig: "RoundedBottomRectangle" },
    { fig: "RoundedBottomRectangle", p1: 0 },
    { fig: "RoundedBottomRectangle", p1: 3 },
    { fig: "RoundedBottomRectangle", p1: 10 },
    { fig: "RoundedBottomRectangle", p1: 50 },
    { fig: "RoundedBottomRectangle", p1: 250 }
  ]);
</pre>
<script>goCode("defineFigure", 700, 300)</script>
<p>
Note how the <a>Shape.parameter1</a> property, data bound to the "p1" property, controls how rounded the corners are.
The definition of each figure limits the roundedness based on the actual size of the geometry.
You can see the effects by resizing the last shape -- the curve on the shape with p1==250 can be huge if the shape becomes huge.
</p>
<p>
You can find the definitions for many figures at: <a href="../extensions/Figures.js" target="_blank">Figures.js</a>.
The "RoundedTopRectangle" and "RoundedBottomRectangle" figures shown above are available at:
<a href="../extensions/RoundedRectangles.js" target="_blank">RoundedRectangles.js</a>.
</p>

</div>
</div>
</body>
</html>
